#include "stdafx.h"
#include "ISPLdCMD.h"
#include <stdio.h>

#define USBCMD_TIMEOUT		5000
#define USBCMD_TIMEOUT_LONG	25000

#define printf(...)

ISPLdCMD::ISPLdCMD()
    : m_bOpenPort(FALSE)
    , m_uCmdIndex(18)	//Do not use 0 to avoid firmware already has index 0 occasionally.
{
    //Test();
}

ISPLdCMD::~ISPLdCMD()
{
}

bool ISPLdCMD::Open_Port(BOOL bErrorMsg)
{
    m_uUSB_PID = 0;
    m_strDevPathName = _T("");
    ScopedMutex scopedLock(m_Mutex);

    if (m_bOpenPort) {
        return true;
    }

    if (m_hidIO.OpenDevice(FALSE, 0x0416, 0x3F00)) {	// ISP FW >= 0x30
        m_uUSB_PID = 0x3F00;
    }
    else if (m_hidIO.OpenDevice(FALSE, 0x0416, 0xA316)) {	// ISP FW < 0x30
        m_uUSB_PID = 0xA316;
    }
    else {
        return false;
    }

    m_strDevPathName = m_hidIO.GetDevicePath().c_str();


    m_bOpenPort = TRUE;
    return true;
}

void ISPLdCMD::Close_Port()
{
    m_strDevPathName = _T("");
    ScopedMutex scopedLock(m_Mutex);

    if (!m_bOpenPort) {
        return;
    }

    m_bOpenPort = FALSE;

    m_hidIO.CloseDevice();
}

bool ISPLdCMD::Check_USB_Link()
{
    ScopedMutex scopedLock(m_Mutex);
    return m_bOpenPort ? true : false;
}

BOOL ISPLdCMD::ReadFile(char *pcBuffer, size_t szMaxLen, DWORD dwMilliseconds, BOOL bCheckIndex)
{
    bResendFlag = FALSE;

    while (1) {
        if (!m_bOpenPort) {
            throw _T("There is no Nu-Link connected to a USB port.");
        }

        DWORD dwLength;

        if (!m_hidIO.ReadFile(m_acBuffer, 65, &dwLength, dwMilliseconds)) {
            return FALSE;
        }


        /* Check if correct package index was read */
        //m_acBuffer[0];	//For HID internal usage, ignored.
        USHORT usCheckSum = *((USHORT *)&m_acBuffer[1]);
        ULONG uCmdIndex = *((ULONG *)&m_acBuffer[5]);

        if (dwLength >= 8
            && (!bCheckIndex || uCmdIndex == m_uCmdIndex - 1)
            && usCheckSum == m_usCheckSum) {
            if (szMaxLen > dwLength - 8) {
                szMaxLen = dwLength - 8;
            }

            if (pcBuffer != NULL && szMaxLen > 0) {
                memcpy(pcBuffer, m_acBuffer + 9, szMaxLen);
            }

            return TRUE;
        }
        else if (m_uUSB_PID == 0xA316) {
            SleepEx(10, TRUE);
        }
        else {
            printf("dwLength = %d, uCmdIndex = %d, %d, usCheckSum = %d, %d\n", dwLength, uCmdIndex, m_uCmdIndex, usCheckSum, m_usCheckSum);
            bResendFlag = TRUE;
            break;
        }
    }

    return FALSE;
}

BOOL ISPLdCMD::WriteFile(unsigned long uCmd, const char *pcBuffer, DWORD dwLen, DWORD dwMilliseconds)
{
    if (!m_bOpenPort) {
        throw _T("There is no Nu-Link connected to a USB port.");
    }

    /* Set new package index value */
    DWORD dwCmdLength = dwLen;

    if (dwCmdLength > sizeof(m_acBuffer) - 9) {
        dwCmdLength = sizeof(m_acBuffer) - 9;
    }

    memset(m_acBuffer, 0, sizeof(m_acBuffer));
    //m_acBuffer[0] = 0x00;	//Always 0x00
    *((ULONG *)&m_acBuffer[1]) = uCmd;
    *((ULONG *)&m_acBuffer[5]) = m_uCmdIndex;

    if (pcBuffer != NULL && dwCmdLength > 0) {
        memcpy(m_acBuffer + 9, pcBuffer, dwCmdLength);
    }

    m_usCheckSum = Checksum((unsigned char *)&m_acBuffer[1], sizeof(m_acBuffer) - 1);
    DWORD dwLength;
    BOOL bRet = FALSE;

    bRet = m_hidIO.WriteFile(m_acBuffer, 65, &dwLength, dwMilliseconds);

    if (bRet != FALSE) {
        m_uCmdIndex += 2;
    }
    else {
        Close_Port();
    }

    printf("Write Cmd : %X\n", uCmd);
    return bRet;
}

void ISPLdCMD::SyncPackno()
{
    m_uCmdIndex = 1;
    WriteFile(
        CMD_SYNC_PACKNO,
        (const char *)&m_uCmdIndex,
        4,
        USBCMD_TIMEOUT);
    ReadFile(NULL, 0, USBCMD_TIMEOUT, FALSE);
}

unsigned char ISPLdCMD::GetVersion()
{
    WriteFile(
        CMD_GET_VERSION,
        NULL,
        0,
        USBCMD_TIMEOUT);
    unsigned char ucVersion;
    ReadFile((char *)&ucVersion, 1, USBCMD_TIMEOUT, TRUE);
    return ucVersion;
}

unsigned long ISPLdCMD::GetDeviceID()
{
    WriteFile(
        CMD_GET_DEVICEID,
        NULL,
        0,
        USBCMD_TIMEOUT);
    unsigned long uID;
    ReadFile((char *)&uID, 4, USBCMD_TIMEOUT, TRUE);
    return uID;
}

void ISPLdCMD::ReadConfig(unsigned int config[])
{
    WriteFile(
        CMD_READ_CONFIG,
        NULL,
        0,
        USBCMD_TIMEOUT);
    ReadFile((char *)config, 16, USBCMD_TIMEOUT, TRUE);
}

void ISPLdCMD::UpdateConfig(unsigned int config[], unsigned int response[])
{
    WriteFile(
        CMD_UPDATE_CONFIG,
        (const char *)config,
        16,
        USBCMD_TIMEOUT_LONG);
    ReadFile((char *)response, 16, USBCMD_TIMEOUT_LONG, TRUE);
}

void ISPLdCMD::UpdateAPROM(unsigned long start_addr,
    unsigned long total_len,
    unsigned long cur_addr,
    const char *buffer,
    unsigned long *update_len)
{
    unsigned long write_len = total_len - (cur_addr - start_addr);
    char acBuffer[
        HID_MAX_PACKET_SIZE_EP
            - 8 /* cmd, index */];

    if (start_addr == cur_addr) {
        if (write_len > sizeof(acBuffer) - 8/*start_addr, total_len*/) {
            write_len = sizeof(acBuffer) - 8/*start_addr, total_len*/;
        }

        memcpy(&acBuffer[0], &start_addr, 4);
        memcpy(&acBuffer[4], &total_len, 4);
        memcpy(&acBuffer[8], buffer, write_len);
        WriteFile(
            CMD_UPDATE_APROM,
            acBuffer,
            write_len + 8/*start_addr, total_len*/,
            USBCMD_TIMEOUT_LONG);
        /* First block need erase the chip, need long timeout */
        ReadFile(NULL, 0, USBCMD_TIMEOUT_LONG, TRUE);
    }
    else {
        if (write_len > sizeof(acBuffer)) {
            write_len = sizeof(acBuffer);
        }

        WriteFile(
            0,
            buffer,
            write_len,
            USBCMD_TIMEOUT_LONG);
        ReadFile(NULL, 0, USBCMD_TIMEOUT_LONG, TRUE);
    }

    if (update_len != NULL) {
        *update_len = write_len;
    }
}

void ISPLdCMD::UpdateNVM(unsigned long start_addr,
    unsigned long total_len,
    unsigned long cur_addr,
    const char *buffer,
    unsigned long *update_len)
{
    unsigned long write_len = total_len - (cur_addr - start_addr);
    char acBuffer[
        HID_MAX_PACKET_SIZE_EP
            - 8 /* cmd, index */];

    if (start_addr == cur_addr) {
        if (write_len > sizeof(acBuffer) - 8/*start_addr, total_len*/) {
            write_len = sizeof(acBuffer) - 8/*start_addr, total_len*/;
        }

        memcpy(&acBuffer[0], &start_addr, 4);
        memcpy(&acBuffer[4], &total_len, 4);
        memcpy(&acBuffer[8], buffer, write_len);
        WriteFile(
            CMD_UPDATE_DATAFLASH,
            acBuffer,
            write_len + 8/*start_addr, total_len*/,
            USBCMD_TIMEOUT_LONG);
        /* First block need erase the chip, need long timeout */
        ReadFile(NULL, 0, USBCMD_TIMEOUT_LONG, TRUE);
    }
    else {
        if (write_len > sizeof(acBuffer)) {
            write_len = sizeof(acBuffer);
        }

        WriteFile(
            0,
            buffer,
            write_len,
            USBCMD_TIMEOUT_LONG);
        ReadFile(NULL, 0, USBCMD_TIMEOUT_LONG, TRUE);
    }

    if (update_len != NULL) {
        *update_len = write_len;
    }
}

BOOL ISPLdCMD::EraseAll()
{
    BOOL ret = FALSE;

    if (WriteFile(CMD_ERASE_ALL, NULL, 0, USBCMD_TIMEOUT_LONG)) {
        ret = ReadFile(NULL, 0, USBCMD_TIMEOUT_LONG, TRUE);
    }

    return ret;
}

BOOL ISPLdCMD::CMD_Connect(DWORD dwMilliseconds)
{
    if (m_uUSB_PID == 0xA316) {
        m_uCmdIndex = 1;
    }

    BOOL ret = FALSE;

    if (WriteFile(CMD_CONNECT, NULL, 0, USBCMD_TIMEOUT_LONG)) {
        ret = ReadFile(NULL, 0, dwMilliseconds, FALSE);
    }

    return ret;
}

BOOL ISPLdCMD::CMD_Resend()
{
    BOOL ret = FALSE;

    if (WriteFile(CMD_RESEND_PACKET, NULL, 0, USBCMD_TIMEOUT_LONG)) {
        ret = ReadFile(NULL, 0, USBCMD_TIMEOUT_LONG, FALSE);
    }

    return ret;
}



void ISPLdCMD::Test()
{
    SetInterface(1, _T(""));

    if (!Open_Port()) {
        printf("Open_Port failed\n");
        return;
    }

    printf("Open_Port successed\n");
    SyncPackno();
    unsigned char cVer = GetVersion();
    unsigned long sDID = GetDeviceID();
    unsigned int config[2], config_user[2];
    ReadConfig(config);
    printf("GetVersion: %X\n", cVer);
    printf("GetDeviceID: %X\n", sDID);
    printf("ReadConfig: %X, %X\n", config[0], config[1]);

    //Erase MCU
    EraseAll();

    memcpy(config_user, config, sizeof(config));
    UpdateConfig(config_user, config);


    {
        unsigned int uAddr, uSize;
        unsigned char *pBuffer;

#if 0
        uSize = m_sFileInfo[1].st_size;
        pBuffer = vector_ptr(m_sFileInfo[1].vbuf);
#endif

        for (unsigned long i = 0; i < uSize;) 
        {
            unsigned long uLen;
            UpdateAPROM(uAddr, uSize, uAddr + i,
                (const char *)(pBuffer + i),
                &uLen);

            if (bResendFlag) 
            {
                if (i == 0 || !CMD_Resend()) 
                {
                    //m_eProcSts = EPS_ERR_APROM;
                    return;
                }
            }

            i += uLen;
        }
    }

    {
        unsigned int uAddr, uSize;
        unsigned char *pBuffer;

#if 0
        uAddr = m_uNVM_Addr;
        uSize = m_sFileInfo[1].st_size;
        pBuffer = vector_ptr(m_sFileInfo[1].vbuf);

        if (m_uNVM_Size < uSize) {
            //m_eProcSts = EPS_ERR_SIZE;
            return;
        }
#endif

        SyncPackno();

        for (unsigned long i = 0; i < uSize;) 
        {
            unsigned long uLen;
            UpdateNVM(uAddr, uSize, uAddr + i,
                (const char *)(pBuffer + i),
                &uLen);

            if (bResendFlag) 
            {
                if (i == 0 || !CMD_Resend()) 
                {
                    //m_eProcSts = EPS_ERR_NVM;
                    return;
                }
            }

            i += uLen;
        }
    }
    Close_Port();
}

